/*
 * Copyright (c) 2010, Nikolaus Moll
 * All rights reserved.
 */
package net.miblounge.gradecalculator.core.student;

import net.miblounge.gradecalculator.core.GradeFactoryUtil;
import net.miblounge.gradecalculator.core.descriptors.courses.Course;
import net.miblounge.gradecalculator.core.descriptors.courses.IndividualCourse;
import net.miblounge.gradecalculator.core.descriptors.modulelistitems.Module;
import net.miblounge.gradecalculator.grade.ICourseGrade;
import net.miblounge.gradecalculator.grade.IGradeFactory;
import net.miblounge.gradecalculator.grade.IModuleGrade;

public class StudentModule implements Comparable<StudentModule> {
	
	private final class Result {
		private IModuleGrade grade;
		private boolean isGraded;
		private float ects;

		private Result()
		{
		}
	}
	
	private final Module module;
	private final Student student;

	private final Result[] results;
	
	private final int sortOrder;
	private final IGradeFactory fab;
	
	private int lastHash = 0;
	
	public StudentModule(final int sortOrder, final Student student, final Module module)
	{
		this.module = module;
		this.student = student;
		this.sortOrder = sortOrder;
		fab = GradeFactoryUtil.getInstance().getFab(student.getUniversity().getCountryCode()); 
		
		results = new Result[ExaminationResultType.values().length];
		for (int i = 0; i < results.length; i++) {
			results[i] = new Result();
		}
	}
	
	public Module getModule()
	{
		return module;
	}
	
	private boolean getPassed(final Course c, final ExaminationResult r, final ExaminationResultType type)
	{
        if (c.getUngradedPerformance().isEmpty() && c.getGradedPerformance().isEmpty()) {
        	if (c instanceof IndividualCourse) {
        		return false;
        	}
        }
        
		if (r == null) {
			return false;
		}
		
		if (!c.getUngradedPerformance().isEmpty()) {
			if (!r.getUngradedPassed(type)) {
				return false;
			}
		}

		if (!c.getGradedPerformance().isEmpty()) {
			if (!fab.getValidator().isValid(r.getGrade(type))) {
				return false;
			}
		}
		
		return true;
	}
	
	private void validate()
	{
		final int currentHash = hashCode();
		if (currentHash == lastHash) {
			return;
		}
		lastHash = currentHash;

		for (int i = 0; i < results.length; i++) {
			results[i].ects = 0;
			results[i].isGraded = false;
			results[i].grade = fab.createModuleGrade();
		}

		for (Course course : module.getCourses()) {
			final ExaminationResult result = student.getResult(course.getExamNumber(), course.getIdentifier());
			final Course usedCourse;
			if ((course instanceof IndividualCourse) && (result.getCourseDescription() != null)) {
				usedCourse = result.getCourseDescription();
			} else {
				usedCourse = course;
			}
			
			for (final ExaminationResultType type : ExaminationResultType.values()) {
				if (!getPassed(usedCourse, result, type)) {	// or course...
					continue;
				}

				results[type.ordinal()].ects += course.getECTS(); 

				if (!usedCourse.getGradedPerformance().isEmpty()) {
					final ICourseGrade courseGrade = fab.createCourseGrade(result.getGrade(type), course.getECTS());
					results[type.ordinal()].grade.add(courseGrade);
					results[type.ordinal()].isGraded = true;
				}
				
			}
		}
	}
	
	public IModuleGrade getGrade(final ExaminationResultType passed)
	{
		validate();
		return results[passed.ordinal()].grade;
	}
	
	public float getECTS(final ExaminationResultType passed)
	{
		validate();
		return Math.min(results[passed.ordinal()].ects, module.getECTS());
	}
	
	@Override
	public int compareTo(final StudentModule o) {
		return sortOrder - o.sortOrder;
	}

	public boolean isGraded(final ExaminationResultType passed) {
		validate();
		return results[passed.ordinal()].isGraded;
	}
	
	@Override
	public int hashCode()
	{
		int result = 17;
		for (Course c : module.getCourses()) {
			final ExaminationResult r = student.getResult(c.getExamNumber(), c.getIdentifier());
			result = 31 * result + ( (r == null) ? 0 : r.hashCode() );
			result = 31 * result + Float.floatToIntBits(c.getECTS());
		}
		return result;
	}
}
